//latte main server
//1.0 rebuilt 2019.6.29 Ang 
//1.1 update and recode 2023.4 Ang
const express= require('express')
const bodyParser = require('body-parser')
const multer = require('multer')
const MongoClient= require('mongodb').MongoClient
const ObjectId= require('mongodb').ObjectID
const session= require('express-session')
//const MongoStore= require('connect-mongo')(session); //for save session by mongodb
const Tools = require('./tools')
const config = require('./config')
const Model = require('./model')
const path =require('path')

const PORT = config.server_port || process.env.PORT || 8000;
const HOST = config.host || '0.0.0.0';
const db_url = config.db_url;
const db_name = config.db_name;

//inital
var app=express();
app.set('view engine','ejs');
app.set('views','views/');
// app.use(function(req,res,next){
// 	if(req.hostname != config.host){
// 		res.end('406')
// 	}else{
// 		next()
// 	}
// }); //anti csrf
app.use(bodyParser.urlencoded({ extended: true }) ); //for default enctype="www-form-urlencoded" forum
app.use( 
	session({ secret:'test', cookie:{ path:'/', httpOnly:true, maxAge:2400000 }, resave:false ,saveUninitialized:false 
		/*store:new MongoStore({
			secret:'session',
			url:url
		})*/
	})  
);  //cookie-session midware
var mg = new MongoClient(db_url,{useNewUrlParser:true,connectTimeoutMS:10000});


//GET method api

app.get('/',function(req,res){
	var page =0;
	var skip_n = (page)*config.perPage_posts;
	var ctrl ={
		'site':config.site,
		'nav_topic':config.topic_list,
		'abstract':config.abstract,
		'abstract_len':config.abstract_length,
		'page':page,
		"topic":0,
		'me_login':req.session.login,
		'me_nickname':req.session.login_nickname,
		'me_id':req.session.login_id,
	};
	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res,err);
		}else{
			client.db(db_name).collection('post').find().skip(skip_n).limit(config.perPage_posts).sort({'date':-1}).toArray((err,docs)=>{
				if(err){
					Tools.bad(res,err)
				}else{
					res.render('page',{
						docs,ctrl
					})
				}
			})
		}
	})
	
});

app.get('/page/:page' ,function(req,res){
	//check and format parameters
	//min Page: 1
	var page = Number(req.params.page)-1;
	if(isNaN(page)){ Tools.bad(res,'bad parameters');return };
	if (page<0) {
		res.redirect("/");
		return;
	};
	var skip_n = (page)*config.perPage_posts;
	var ctrl ={
		'site':config.site,
		'nav_topic':config.topic_list,
		'abstract':config.abstract,
		'abstract_len':config.abstract_length,
		'page':page,
		"topic":0,
		'me_login':req.session.login,
		'me_nickname':req.session.login_nickname,
		'me_id':req.session.login_id,
	}
	
	//now ,let's do it (in other routes,will same like thie)
	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res,err);
		}else{
			client.db(db_name).collection('post').find().skip(skip_n).limit(config.perPage_posts).sort({'date':-1}).toArray((err,docs)=>{
				if(err){
					Tools.bad(res,err)
				}else{
					res.render('page',{
						docs,ctrl
					})
				}
			})
		}
	})
});

app.get('/topic/:topic/:page',function(req,res){
	var page = Number(req.params.page), topic = req.params.topic;
	if(isNaN(page) || page<0 || !topic){ Tools.bad(res,'bad parameters');return };
	var skip_n = (page)*config.perPage_posts;
	var ctrl ={
		'site':config.site,
		'nav_topic':config.topic_list,
		'abstract':config.abstract,
		'abstract_len':config.abstract_length,
		'page':page,
		"topic":topic,
		'me_login':req.session.login,
		'me_nickname':req.session.login_nickname,
		'me_id':req.session.login_id,
	}
	
	//now ,let's do it (in other routes,will same like thie)
	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res,err);
		}else{
			client.db(db_name).collection('post').find({'topic':topic}).skip(skip_n).limit(config.perPage_posts).sort({'date':-1}).toArray((err,docs)=>{
				if(err){
					Tools.bad(res,err)
				}else{
					res.render('page',{
						docs,ctrl
					})
				}
			})
		}
	})
});

app.get('/post/:id/:page',function(req,res){
	var page = Number(req.params.page),post_id = req.params.id;
	if(post_id.length != 24 || isNaN(page) || page<0 ){ res.end(Tools.bad(res));return };
	var post_id =ObjectId(req.params.id), skip_n = (page)*config.perPage_replies;
	var ctrl = {
		"site":config.site,
		"page":page,
		"post_topic":req.query.t,
		'me_login':req.session.login,
		'me_nickname':req.session.login_nickname,
		'me_id':req.session.login_id || 'nologin',
	};

	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res)
		}else{
			var db = client.db(db_name);
			var cursor = db.collection('post');
			cursor.findOne({'_id':post_id},(err,doc)=>{
				if(err){
					Tools.bad(res)
				}else if(!doc){
					Tools.bad(res,'no such post')
				}else{
					cursor.updateOne({'_id':post_id},{$inc:{'viewed':1}},(err,back)=>{if(err){console.log('Latte:'+err)}});  //Async update the post date
					//find the nearly replies with post
					db.collection('reply').find({'post_id':req.params.id}).skip(skip_n).limit(config.perPage_replies).sort({'date':-1}).toArray((err,replies)=>{
						if (err) { var replies = [] };
						res.render('post',{
							doc,replies,ctrl
						})
					})
				}
			})
		}
	})
});

app.get('/user/:id',function(req,res){
	if(req.params.id.length != 24 ){ res.end(Tools.bad(res));return };
	var user_id = ObjectId(req.params.id);
	var ctrl = {
		'site':config.site,
		'me_login':req.session.login,
		'me_nickname':req.session.login_nickname,
		'me_id':req.session.login_id,
	}

	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res)
		}else{
			client.db(db_name).collection('user').findOne({'_id':user_id},(err,doc)=>{
				if(err){
					Tools.bad(res,'connet to user colletion faild')
				}else if(!doc){
					Tools.bad(res,'no such user')
				}else{
					res.render('user',{
						doc,ctrl
					})
				}
			})
		}
	})
});

app.get('/editor',function(req,res){
	if(!req.session.login){ Tools.failed(res,'need login before');return };
	var topic_list = config.topic_list;
	var ctrl = {
		'site':config.site,
		'me_login':req.session.login,
		'me_nickname':req.session.login_nickname,
		'me_id':req.session.login_id,
	}

	res.render('editor',{
		topic_list,ctrl
	})
});

app.get('/logout',function(req,res){
	req.session.login = false;
	req.session.login_nickname = null;
	req.session.login_id = null;
	//Tools.ok(res,'success logout');
	res.end('ok logout')

})

//POST nethod api

app.post('/regist',function(req,res){
	var params = req.body;
	if(!params['account'] || !params['pwd']){ Tools.bad(res);return };
	Tools.SAFE_STRING(params);
	var account = params['account'],password= params['pwd'];
	var nickname = account.split('@')[0];
	try{
		var new_user = Model.user(account,password,nickname)
	}catch(err){
		Tools.bad(res,err);return
	}

	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res)
		}else{
			var cursor = client.db(db_name).collection('user');
			cursor.count({'account':account},(err,n)=>{
				if(err){
					Tools.bad(res)
				}else if(n){
					console.log(n)
					Tools.failed(res,'this account have exsit:'+account)
				}else{
					cursor.insertOne(new_user,(err,back)=>{
						if(err || !back.insertedCount){
							Tools.failed(res,'create account:'+account)
						}else{
							//Tools.ok(res,'/',account)
							res.end('ok '+account)
						}
					})
				}
			})
		}
	})
});

app.post('/login',function(req,res){
	var crushed = Tools.antiCrush.checkAction(req,config.ip_block_trigger,config.ip_block_duration);
	if(crushed){ Tools.bad(res,'blocked. after '+crushed+ ',try agian');return }; //anti crush attack
	var params = req.body;
	Tools.SAFE_STRING(params);
	var account = params['account'];
	var pwd = params['pwd'];
	if(!account || !pwd){ Tools.bad(res);return }

	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res)
		}else{
			var cursor = client.db(db_name).collection('user');
			cursor.findOne({'account':account},(err,doc)=>{
				if(err){
					Tools.bad(res)
				}else{
					if(!doc){
						Tools.antiCrush.actionCounter(req);
						Tools.bad(res,'no such user')
					}else{
						if(pwd === doc.pwd){
							req.session.login = true;
							req.session.login_nickname = doc.nickname;
							req.session.login_id = doc._id;
							cursor.updateOne({'account':account},{ $set:{'near_login':new Date()} },(err,back)=>{
								if(err || !back.modifiedCount){
									Tools.failed(res,err)
								}else{
									Tools.ok(res,'/','you been login: '+account)
								}
							})
						}else{
							Tools.antiCrush.actionCounter(req);
							Tools.failed(res,"password don't match account")
						}
					}
				}
			})
		}
	})
});

app.post('/add_post',function(req,res){
	if(Tools.resubmit(req.session.login_posted,5000)){
		Tools.bad(res,'posting much often');return
	}else{
		req.session.login_posted = new Date;  // dissatisfactory measure
	};
	var ownner =req.session.login_id,nickname = req.session.login_nickname;
	if(!ownner){ Tools.failed(res,'need login frist');return };
	var params = req.body;
	if(!params['title'] || !params['content']){ Tools.failed(res,'cant not post with noting or post too frequently');return };
	// client.db(db_name).collection('user').findOne({'_id':ownner},(err,doc)=>{
	// 	var last = doc['ear_login'];

	// })
	Tools.SAFE_STRING(params);
	/**base hte Ejs auto trans,only need hanlde parmas['content'] of POST, and in post.ejs will not whith transfer */
	Tools.SAFE_CONTENT(params);
	try{
		var new_post = Model.post(ownner,params['title'],params['content'],params['topic'],nickname)
	}catch(err){
		Tools.failed(res,err);return
	};
	var ownner_in_user = ObjectId(ownner);

	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res)
		}else{
			client.db(db_name).collection('post').insertOne(new_post,(err,back)=>{
				if(err || !back.insertedCount){
					Tools.failed(res,'add post failed')
				}else{
					client.db(db_name).collection('user').findOneAndUpdate({'_id':ownner_in_user},{$addToSet:{'posts':{'title':params['title'],'id':back.insertedId,'near_login':new Date()}}},(err)=>{  if(err) { console.log(err) } }); //Async update the user date
					Tools.ok(res,'/post/'+back.insertedId+'/0','post'+back.insertedId)
				}
			})
		}
	})
});

app.post('/add_reply',function(req,res){
	if(Tools.resubmit(req.session.login_posted,5000)){
		Tools.bad(res,'posting much often');return
	}else{
		req.session.login_posted = new Date;  // dissatisfactory measure
	};
	var ownner = req.session.login_id;
	var nickname = req.session.login_nickname;
	if(!ownner){ Tools.failed(res,'need login frist');return };
	var params = req.body;
	if(!params['content'] || !params['post_id']){ Tools.failed(res,'cant not post with nothing or post too frequently');return };
	Tools.SAFE_STRING(params);
	Tools.SAFE_CONTENT(params);
	try{
		var new_reply = Model.reply(ownner,params['content'],params['post_id'],nickname)
	}catch(err){
		Tools.failed(res,'err');return
	};
	var post_id_in_post = ObjectId(params['post_id']);
	var ownner_in_user = ObjectId(ownner);

	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res)
		}else{
			client.db(db_name).collection('reply').insertOne(new_reply,(err,back)=>{
				if(err || !back.insertedCount){
					Tools.failed(res,'add reply failed')
				}else{
					client.db(db_name).collection('post').updateOne({'_id':post_id_in_post},{$inc:{'reply_n':1}},(err,back)=>{if(err){console.log('Latte: '+err)}}); //Aysnc update the post data
					client.db(db_name).collection('user').findOneAndUpdate({'_id':ownner_in_user},{$addToSet:{'replies':{'title':params['post_title'],'id':params['post_id']}}},(err)=>{  if(err) { console.log(err) } }); //Async update the user date
					Tools.ok(res,'/post/'+params['post_id']+'/0','reply'+back.insertedId)
				}
			})
		}
	})
});

//AJAX

app.get('/rm/:type/:id',function(req,res){
	const type_list ={
		'reply':'reply',
		'post':'post',
		'discuz':'discuz'
	};
	var user_type_list = {
		'reply':'replies','post':'posts'
	}
	if(!req.session.login){ Tools.bad(res,'need login before');return };
	var ownner = req.session.login_id;
	var type = type_list[req.params['type']], item_id = ObjectId(req.params['id']),ownner_in_user = ObjectId(ownner);
	var user_type = user_type_list[req.params['type']];
	if(!type || !item_id || !ownner){ Tools.bad(res,'bad parameters');return };

	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res,err)
		}else{
			var cursor = client.db(db_name).collection(type);
			cursor.findOne({'_id':item_id},(err,doc)=>{
				if(err){
					Tools.bad(res,err)
				}else{
					if(doc && doc.ownner == ownner){
						cursor.deleteOne({'_id':item_id},(err,back)=>{
							/* back ??cant check it BUG*/
							if(err){
								Tools.failed(res,'remove '+item_id)
							}else{
								client.db(db_name).collection('user').findOneAndUpdate({'_id':ownner_in_user},{$pop:{ user_type:{$eq:{'id':req.params['id']}}}},(err,back)=>{
									if(err)console.log(err) })
								res.end('ok removed')
							}
						})
					}else{
						Tools.bad(res,'bad request')
					}
				}
			})
		}
	})

});

app.post('/add_discuz',function(req,res){
	if(Tools.resubmit(req.session.login_posted,5000)){
		Tools.bad(res,'posting much often');return
	}else{
		req.session.login_posted = new Date;  // dissatisfactory measure
	};
	var params = req.body,ownner = req.session.login_id,nickname = req.session.login_nickname;
	if(!ownner){ Tools.bad(res,'need login');return };
	if(!params['reply_id'] || !params['content']){ Tools.bad(res);return };
	Tools.SAFE_STRING(params);
	var reply_id_in_reply = ObjectId(params['reply_id'])

	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res);retrun
		}else{
			client.db(db_name).collection('reply').findOneAndUpdate({'_id':reply_id_in_reply},{$addToSet:{'discuz':{'id':new ObjectId,'ownner':ownner,'nickname':nickname,'date':new Date,'content':params['content']}}},(err,back)=>{
				if(err){
					Tools.failed(res,'add discuss failed')
				}else{
					res.end('ok add discuss')
				}
			})
		}
	})
});

app.get('/star/:post_id',function(req,res){
	if(!req.session.login){ Tools.bad(res,'need login before');return };
	var post_id = req.params['post_id'];
	if(!post_id){ Tools.bad(res,'bad parameters');return };
	var current_user = ObjectId(req.session.login_id),post_id_in_post = ObjectId(post_id);

	mg.connect((err,client)=>{
		if(err){
			Tools.bad(res)
		}else{
			var cursor = client.db(db_name).collection('user')
			cursor.findOne({'_id':current_user},(err,doc)=>{
				if(!doc) { Tools.bad(res,'no such user');return }
				if( doc['star_list'].indexOf(post_id) >=0 ){
					Tools.failed(res,'you have been star it');return  //there may abug when this step ok and next failed.
				}else{
					client.db(db_name).collection('post').findOneAndUpdate({'_id':post_id_in_post},{$inc:{'star':1}},(err,back)=>{
						if(err){
							Tools.bad(res)
						}else{
							cursor.findOneAndUpdate({'_id':current_user},{$addToSet:{'star_list':post_id}}) //aysnc updata user data
							res.end('ok star')
						}
					})
				}
			})
		}
	})
});


//////////
app.get('/test',(req,res)=>{
	var msg ='Latte ok :\n';
	msg+=req.ip+'\n'+req.get('User-Agent')+'\n';
	res.end(msg)
});

app.use('/',express.static(path.resolve(__dirname,'static'),{maxAge:'2h'}));

app.listen(PORT,HOST,()=>{ console.log('latte running on '+HOST+':'+PORT)})